﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Xml.Serialization;
using JetBrains.Annotations;

namespace ZOIL.DataModel
{
    public abstract class InformationObjectBase : IInformationItem, INotifyPropertyChanged
    {
        protected InformationObjectBase()
        {
            _links = CollectionFactoryProvider.Factory.CreateObservableCollection<ILink>();
        }

        //TODO: Name auf Id ändern, BelongsToDatabaseId auf BelongsToId ändern
        public long DatabaseId { get; set; }

        #region ILink
        private ObservableCollection<ILink> _links;

        //Link auf andere IPersistable -> Wird für BaseX nicht referenzgleich deserialisiert
        [XmlIgnore]
        public ObservableCollection<ILink> Links
        {
            get
            {
                return _links;
            }
            set
            {
                if (Equals(value, _links))
                {
                    return;
                }
                _links = value;
                RaisePropertyChanged("Links");
                OnObjectLocallyChanged();
            }
        }
        public void AddLink(ILink link)
        {
            Links.Add(link);
            OnObjectLocallyChanged();
        }

        public void RemoveLink(ILink link)
        {
            Links.Remove(link);
            OnObjectLocallyChanged();
        }

        #region XmlSerializer ist doof...

        [XmlArray("Links")]
        [XmlArrayItem("Link")]
        public LinkXmlSerializable[] LinksToXmlSerialize
        {
            get
            {
                return Links != null ? Links.Select(LinkXmlSerializable.Create).ToArray() : null;
            }
            set 
            { 
                _links = value != null ? new ObservableCollection<ILink>(value.Select(LinkXmlSerializable.ToLink)) : null;
                RaisePropertyChanged("Links");
            }
        }

        #endregion

        #endregion

        public event PropertyChangedEventHandler PropertyChanging;

        [NotifyPropertyChangedInvocator]
        protected virtual void RaisePropertyChanging(string propertyName)
        {
            var handler = PropertyChanging;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void RaisePropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public virtual void OnRemoteUpdated()
        {
            RaisePropertyChanged("Links");
        }

        [XmlIgnore]
        [Db4oIgnore]
        private static readonly EqualityComparer<ILink> LinksQualitityComparer = new EqualityComparer<ILink>((l1, l2) => l1.Node1.DatabaseId == l2.Node1.DatabaseId && l1.Node2.DatabaseId == l2.Node2.DatabaseId, l => l.Node1.DatabaseId.GetHashCode() ^ l.Node2.DatabaseId.GetHashCode());

        protected static readonly string LinksFieldName = "_links";

        /// <summary>
        /// Updates the <see cref="Links"/> without changing the Reference of the <see cref="Links"/> collection -> add, remove, clear
        /// 
        /// Must be called in <see cref="ICustomUpdateable.OnUpdate"/> implementation. Use <see cref="LinksFieldName"/> to exclude Links from further updates via exchanging fields via reflection (-> <code> this.ExchangeFieldsOnFirstLevel(remotelyUpdatedObject, new string[] {LinksFieldName});</code>
        /// </summary>
        protected void CustomUpdateLinks(ObservableCollection<ILink> updatedLinks)
        {
            //Links selber updaten damit referenz gleich bleibt (-> INotifyCollectionChanged)
            var cLinks = Links;
            if (updatedLinks == null && cLinks == null)
            {
                //nichts machen
            }
            else if (updatedLinks == null)
            {
                _links = null;
            }
            else if (cLinks == null)
            {
                _links = updatedLinks;
            }
            else if (cLinks.Count == 0 && updatedLinks.Count == 0)
            {
                //nichts machen
            }
            else if (updatedLinks.Count == 0)
            {
                cLinks.Clear();
            }
            else
            {
                //beide haben werte

                var added = updatedLinks.Except(cLinks, LinksQualitityComparer).ToArray();
                var removed = cLinks.Except(updatedLinks, LinksQualitityComparer).ToArray();

                foreach (var l in removed)
                {
                    cLinks.Remove(l);
                }
                foreach (var l in added)
                {
                    cLinks.Add(l);
                }
            }
        }

        public event EventHandler ObjectLocallyChanged;
        protected virtual void OnObjectLocallyChanged()
        {
            var handler = ObjectLocallyChanged;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }
    }
}